//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using JetBrains.Annotations;
namespace LargoCommon.Music
{
///
/// Musical Block Model.
///
[Serializable]
public sealed class RhythmicModel : AbstractModel, ICloneable
{
#region Fields
///
/// Rhythmic motives.
///
[NonSerialized]
private IEnumerable rhythmicMotives;
/// Used Rhythmic Motives.
private Dictionary usedRhythmicMotives;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
public RhythmicModel() {
this.RhythmicMotives = new List();
}
///
/// Initializes a new instance of the class.
///
/// The mark block model.
public RhythmicModel(XElement markBlockModel) {
this.RhythmicMotives = new List();
}
#endregion
#region Properties - Xml
/// Gets Xml representation.
/// Property description.
public new XElement GetXElement {
get {
var xe = new XElement(
"Rhythmic",
new XAttribute("Name", this.Name),
new XAttribute("Order", this.RhythmicOrder));
var xmotives = new XElement("Motives");
foreach (var rstruct in this.RhythmicMotives) {
var xmotive = rstruct.GetXElement;
xmotives.Add(xmotive);
}
xe.Add(xmotives);
return xe;
}
}
#endregion
#region Properties
///
/// Gets or sets the rhythmic order.
///
///
/// The rhythmic order.
///
public byte RhythmicOrder {
get; set;
}
///
/// Gets or sets the rhythmic motives.
///
///
/// The rhythmic motives.
///
public IEnumerable RhythmicMotives {
get {
Contract.Ensures(Contract.Result>() != null);
if (this.rhythmicMotives == null) {
throw new InvalidOperationException("Rhythmic motives are null.");
}
return this.rhythmicMotives;
}
set => this.rhythmicMotives = value ?? throw new ArgumentException("Argument cannot be empty.", nameof(value));
}
///
/// Gets the rhythmic structures by variance.
///
/// Property description.
public IList RhythmicStructuresOfMotives {
get {
Contract.Ensures(Contract.Result>() != null);
var rs = new List();
foreach (var m in this.RhythmicMotives) {
rs.AddRange(m.RhythmicStructures);
}
var structs = (from r in rs select r).Distinct().ToList();
//// var sortedStructs = from rx in structs orderby rx.Level, rx.Variance select rx; //// rx.ToneLevel rx.ElementSchema
//// OrderBy(x => x.Level.ToString().PadLeft(3) + x.ElementSchema).ToList(); ////. x.ElementSchema);
return structs;
}
}
#endregion
#region Other public properties
///
/// Gets the first melodic bar.
///
/// Property description.
[UsedImplicitly]
public int FirstMelodicBar {
get {
var barNumber = (from c in this.BlockChanges.Changes where c.IsMelodicalNature orderby c.BarNumber select c.BarNumber).FirstOrDefault();
return barNumber;
}
}
#endregion
#region Static factory methods
///
/// Extracts the musical block model.
///
/// The musical block.
///
/// Returns value.
///
[UsedImplicitly]
public static RhythmicModel GetNewModel(MusicalBlock musicalBlock) {
musicalBlock.Header.NumberOfLines = (byte)musicalBlock.Strip.Lines.Count;
var model = GetNewModel(musicalBlock.Header.Name, musicalBlock);
model.Number = musicalBlock.Header.Number;
model.SourceMusicalBlock = musicalBlock;
model.Header = musicalBlock.Header;
return model;
}
///
/// Gets the new musical model.
///
/// Name of the model.
/// The musical block.
///
/// Returns value.
///
/// Null Exception.
public static RhythmicModel GetNewModel(string modelName, MusicalBlock musicalBlock) {
Contract.Requires(musicalBlock != null);
var model = new RhythmicModel {
Name = modelName,
IsSelected = false
};
if (model == null) {
throw new ArgumentNullException(nameof(modelName));
}
model.Header = musicalBlock.Header;
model.SourceMusicalBlock = musicalBlock;
model.RhythmicOrder = model.Header.System.RhythmicOrder;
return model;
}
#endregion
#region Public methods
/* 2018/10
///
/// Gets the rhythmic structures.
///
/// The number of top rhythmic structures to select.
/// The regularity.
/// From level.
/// To level.
///
/// Returns value.
///
[UsedImplicitly]
public IList GetRhythmicStructures(int numberOfTopRhythmicStructuresToSelect, RegularityFactor regularity, byte fromLevel, byte toLevel)
{
//// Warning 9 CodeContracts: Member 'LargoObjectMusic.Core.RhythmicCore.rhythmicMotives' has less visibility than the enclosing method
//// 'LargoObjectMusic.Core.RhythmicCore.GetRhythmicStructures(System.Int32,System.Boolean,System.Byte,System.Byte)'
Contract.Requires(this.rhythmicMotives != null);
List list;
if (regularity == RegularityFactor.Regular) {
list = this.RegularRhythmicStructures(2).ToList();
}
else {
list = (from rs in this.RhythmicStructuresOfMotives
where rs.FormalBehavior.Variance > 1 && rs.FormalBehavior.Variance < DefaultValue.NinetyNine && (rs.ZeroStart || rs.ToneLevel == 0)
select rs).ToList();
}
var resultList = (from rs in list
where rs.ToneLevel >= fromLevel && rs.ToneLevel <= toLevel
orderby rs.ToneLevel descending, rs.RhythmicBehavior.Filling descending, rs.FormalBehavior.Variance descending
select rs).Take(numberOfTopRhythmicStructuresToSelect).ToList();
return resultList;
}
*/
///
/// Converts to order.
///
/// The given system.
[UsedImplicitly]
public void ConvertToSystem(RhythmicSystem givenSystem) {
Contract.Requires(givenSystem != null);
this.RhythmicOrder = givenSystem.Order;
var cnt = this.RhythmicMotives.Count();
if (cnt == 0) {
return;
}
foreach (var m in this.RhythmicMotives) {
m.ConvertToSystem(givenSystem);
}
}
///
/// Gets the rhythmic motive.
///
/// The number.
/// Returns value.
[UsedImplicitly]
public RhythmicMotive GetRhythmicMotive(int number) {
var cnt = this.RhythmicMotives.Count();
if (cnt == 0) {
return null;
}
var localNumber = number;
if (localNumber > cnt) {
checked {
localNumber = ((number - 1) % cnt) + 1;
}
}
//// RhythmicMotive motive = this.RhythmicMotives.ElementAt(localNumber);
var motive = (from m in this.RhythmicMotives where m.Number == localNumber select m).FirstOrDefault();
return motive;
}
///
/// Adds the motive.
///
/// The motive.
public void AddMotive(RhythmicMotive motive) {
Contract.Requires(motive != null);
//// motive.Core = this;
((List)this.RhythmicMotives).Add(motive);
}
#endregion
///
/// Appends the rhythmic motives.
/// Main algorithm to determine motivic classes and their instances
///
/// The item groups.
public void AppendRhythmicMotives(List itemGroups) {
var lastRhythmicIdentifier = string.Empty;
//// All motivic items ordered by length (descending) and identifier
var rhythmicItemGroups = (from ig in itemGroups orderby ig.Length descending, ig.RhythmicIdentifier select ig).ToList();
RhythmicMotive rhythmicMotive = null;
//// var numberWithinLength = 0; var lastLength = 0;
foreach (var itemGroup in rhythmicItemGroups) {
//// if (itemGroup.Length != lastLength) { numberWithinLength = 0; lastLength = itemGroup.Length; }
var ident = itemGroup.RhythmicIdentifier;
if (ident != lastRhythmicIdentifier) { //// Step to next new motive
rhythmicMotive = itemGroup.RhythmicMotive(0, string.Empty);
if (rhythmicMotive == null) {
return;
}
this.AddMotive(rhythmicMotive);
lastRhythmicIdentifier = ident;
}
if (rhythmicMotive != null) {
rhythmicMotive.Occurrence++;
var area = itemGroup.GetArea();
this.SourceMusicalBlock.Body.MarkRhythmicMotive(rhythmicMotive, area);
}
}
this.CompleteMotives();
}
///
/// Completes the motives.
///
public void CompleteMotives() {
var rhythmicMotiveNumber = 0;
var orderedMotives = (from m in this.RhythmicMotives orderby m.Occurrence descending, m.Length descending select m).ToList();
foreach (var motive in orderedMotives) {
rhythmicMotiveNumber++;
//// string motiveName = string.Format(CultureInfo.InvariantCulture,"T{0}/R{1}", ("0" + musicalTrack.LineIndex.ToString(CultureInfo.CurrentCulture)).Right(2), ("0000" + this.MelodicAnalyzer.RhythmicMotiveNumber.ToString(CultureInfo.CurrentCulture)).Right(4));
//// var motiveName = string.Format(CultureInfo.InvariantCulture, "R{0}", ("0000" + rhythmicMotiveNumber.ToString(CultureInfo.CurrentCulture)).Right(4));
var motiveName = MusicalProperties.GetMotiveName(string.Empty, rhythmicMotiveNumber, motive.Length); //// rhythmicMotiveNumber
motive.Number = rhythmicMotiveNumber;
motive.Name = motiveName;
}
}
#region Unique motives
///
/// Get UniqueTRhythmicMotive.
///
/// Unique Identifier.
/// Returns value.
[UsedImplicitly]
public RhythmicMotive GetUniqueRhythmicMotive(string uniqueIdentifier) {
Contract.Requires(uniqueIdentifier != null);
if (this.usedRhythmicMotives == null) {
this.usedRhythmicMotives = new Dictionary();
}
var trm = this.usedRhythmicMotives.ContainsKey(uniqueIdentifier) ? this.usedRhythmicMotives[uniqueIdentifier] : null;
if (trm != null) {
return trm;
}
//// Lock needed here
var rhythmicMotiveList = this.RhythmicMotives;
foreach (var rhythmicMotive in
rhythmicMotiveList.Where(rhythmicMotive => rhythmicMotive != null && string.CompareOrdinal(rhythmicMotive.UniqueIdentifier, uniqueIdentifier) == 0)) {
this.usedRhythmicMotives[uniqueIdentifier] = rhythmicMotive;
return rhythmicMotive; //// Avoid multiple or conditional return statements.
}
return null;
}
#endregion
#region String representation
///
/// Returns a that represents this instance.
///
///
/// A that represents this instance.
///
[UsedImplicitly]
public override string ToString() {
return this.RhythmicOrder.ToString(CultureInfo.InvariantCulture);
}
#endregion
/// Makes a deep copy of the BlockModel object.
/// Returns object.
public object Clone() {
var model = new RhythmicModel {
Name = this.Name,
Number = this.Number,
//// Core = this.Core,
Header = (MusicalHeader)this.Header.Clone(),
SourceMusicalBlock = this.SourceMusicalBlock
};
return model;
}
#region Material Extractor
///
/// Extract rhythmic material.
///
///
/// Returns value.
///
public RhythmicMaterial ExtractRhythmicMaterial() {
//// var dcm = DataBridgeMaterial.GetMaterialContext;
var material = new RhythmicMaterial { Name = this.Name, RhythmicOrder = this.RhythmicOrder }; //// , CoreId = rhythmicCore.TRhythmicCore.Id
//// dcm.AddToTRhythmicMaterial(trm);
var list = new Collection();
// ReSharper disable once LoopCanBePartlyConvertedToQuery
foreach (var motive in this.RhythmicMotives) {
// ReSharper disable once LoopCanBePartlyConvertedToQuery
foreach (var structure in motive.RhythmicStructures) {
var sc = structure.GetStructuralCode;
if (string.IsNullOrEmpty(sc)) {
continue;
}
list.Add(structure);
}
}
//// Grouping
var groupList = (from ms in list
group ms by ms.GetStructuralCode into g
select g).ToList();
foreach (var g in groupList) {
var s = g.FirstOrDefault();
if (s == null) {
continue;
}
var ms = s; //// Clone? //// new RhythmicStructure(s.RhythmicSystem, s.GetStructuralCode())
ms.Occurrence = g.Count();
ms.DetermineLevel(); //// 2019/01
ms.DetermineBehavior(); //// 2019/01
material.Structures.Add(ms);
}
//// dcm.SaveChanges();
return material;
}
#endregion
#region Private methods
///
/// Regulars the rhythmic structures.
///
/// The metric base.
///
/// Returns value.
///
[UsedImplicitly]
private IEnumerable RegularRhythmicStructures(byte metricBase) {
Contract.Ensures(Contract.Result>() != null);
var rsystem = RhythmicSystem.GetRhythmicSystem(RhythmicDegree.Structure, this.RhythmicOrder);
var structs = new List();
var s1 = string.Format(CultureInfo.CurrentCulture, "1,{0}*0,", this.RhythmicOrder - 1);
var r1 = new RhythmicStructure(rsystem, s1);
r1.DetermineBehavior();
structs.Add(r1);
var s1P = string.Format(CultureInfo.CurrentCulture, "2,{0}*0,", this.RhythmicOrder - 1);
var r1P = new RhythmicStructure(rsystem, s1P);
r1P.DetermineBehavior();
structs.Add(r1P);
for (var d = 1; d < this.RhythmicOrder - 1; d++) {
if (d % metricBase != 0) {
continue;
}
if (this.RhythmicOrder % d != 0) {
continue;
}
var sb = new StringBuilder();
var length = this.RhythmicOrder / d;
var s = string.Format(CultureInfo.CurrentCulture, "1,{0}*0,", length - 1);
for (var i = 0; i < d; i++) {
sb.Append(s);
}
sb.Remove(sb.Length - 1, 1);
var rs = new RhythmicStructure(rsystem, sb.ToString());
rs.DetermineBehavior();
structs.Add(rs);
}
return structs;
}
#endregion
}
}